/*
Borealis Library
A library of commonly used hydrological modeling building blocks

Copyright (C) 2008  Chris Marsh

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef OBSERVATION_H
#define OBSERVATION_H


#include <string>
#include <fstream>
#include <vector>


#include <boost/date_time/posix_time/posix_time.hpp> // for boost::posix
#include <boost/crc.hpp>      // for boost::crc_basic, boost::crc_optimal
#include <boost/cstdint.hpp>  // for boost::uint16_t
#include <boost/variant.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/utility.hpp>


#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_vector.h>

#include "RegexTokenizer.hpp"
#include "Logger.hpp"


namespace Borealis
{

	
	/*
		Class: Observation
			Holds the meterological data.
			Observation classes must currently not be copied or assigned from other instances.
	*/
	class Observation : boost::noncopyable
	{
	private:

		// Used to compare the hashes of the main data structure
		//not case sensitive
		class HashCompare
		{
		public:
			static size_t hash( const std::string& x);
			static bool equal(const std::string& s1, const std::string& s2);
		};

		//typedefs must go here after hashcompare decl

		typedef boost::variant< float,int,std::string, boost::posix_time::ptime > Datatypes;
		typedef tbb::concurrent_vector< Datatypes > Variable;
		typedef tbb::concurrent_hash_map<std::string, Variable ,HashCompare> ObsTable;


		//This is a hashmap interface, vector back end
		// "var1"        |     "var2"     |     "var3"   |      hashmap< std::string, tbb::concurrent_vector >
		// ------------------------------------------------      
		//      [...]    |      [...]     |      [...]   |
		//    vector 1   |     vector 2   |     vector 3 |
		//      [...]    |      [...]     |      [...]   |
		ObsTable m_variables;

		int m_cols;
		int m_rows;
		bool m_isOpen;

	public:
		/*
		Class: Timestep
		A given time step. Returned from dereferencing a Observation::const_iterator. Not to be directly instantiated. 
		Cannot directly modify the observation data

		Example:
		>Borealis::Observation::const_iterator itr;
		>[...]
		>(*itr).Get<std::string>("Date")
		*/
		class Timestep
		{
		public:

			/*
			Function: Timestep
			Default empty constructor

			Parameters: 

			Throws:
			Never

			Returns:   
			- 
			*/
			Timestep();


			/*
			Function: Timestep
			Copy constructor

			Parameters: 
			const Timestep & src - 

			Throws:
			Never

			Returns:   
			- 
			*/
			Timestep(const Timestep& src);

			~Timestep();

			/*
			Function: ToString
			Converts the current time step to a std::string. There is no guarantee made about variable output order. 

			Parameters: 

			Throws:
			Never

			Returns:   
			std::string - Variables. Dates will be in the form 2008-Feb-23 23:59:59
			*/
			std::string ToString();


			/*
				Function: Month
					 Gets the current month. At worst, will do O(n) where n=number of variables headers.
					 If this function is called many times, or more calculations are needed it might be best either save the value or use "GetGreorian" or "GetPosix" methods. 
				
				Parameters: 
					None

				Throws:
					Never
				
				Returns:   
					std::string - Current month number, starting with Jan = 1. If the date can't be determined (perhaps there is no date value) returns -1
			*/
			int Month();


			/*
				Function: Day
					 Gets the current day number. At worst, will do O(n) where n=number of variables headers.
					 If this function is called many times, or more calculations are needed it might be best either save the value or use "GetGreorian" or "GetPosix" methods. 
				
				Parameters: 
					None

				Throws:
					Never
				
				Returns:   
					int - Current day number starting at 1. -1 on error
			*/
			int Day();


			/*
				Function: Year
					 Gets the current year. At worst, will do O(n) where n=number of variables headers.
					 If this function is called many times, or more calculations are needed it might be best either save the value or use "GetGreorian" or "GetPosix" methods. 
				
				Parameters: 
					None

				Throws:
					Never
				
				Returns:   
					int - Current year. -1 on error
			*/
			int Year();

			/*
				Function: GetGregorian
					 Returns the boost::gregoiran structure for time calculations. Performs at worst O(n) where n=number of variable headers
					 See <http://www.boost.org/doc/libs/1_38_0/doc/html/date_time/gregorian.html#date_time.gregorian.date_class>
				
				Parameters: 
					None

				Throws:
					std::runtime_error is there is no date variable in memory
				
				Returns:   
					boost::gregorian::date - boost gregorian date structure.
			*/
			boost::gregorian::date GetGregorian();

			/*
				Function: GetPosix
					  Returns the boost::posix_time structure for time calculations. Performs at worst O(n) where n=number of variable headers
					  See <http://www.boost.org/doc/libs/1_38_0/doc/html/date_time/posix_time.html#date_time.posix_time.ptime_class>

				Parameters: 
					None

				Throws:
					std::runtime_error is there is no date variable in memory
				
				Returns:   
					boost::posix_time::ptime - boost posix time structure
			*/
			boost::posix_time::ptime GetPosix();


			/*
			Function: Get
			Gets the value associated with a variable. The routine will do its best to cast to type T using boost::lexical_cast<T>()

			Parameters: 
			std::string varName - Variable name. Must be the same name that's in the headers, read form the file.

			Throws:
			std::runtime_error - if it cannot be cast to type T

			Returns:   
			boost::T - Value of the variable at the current timestep
			*/
			template<typename T>
			T Get(std::string varName)
			{
				ConstItrMap::const_accessor a;
				T out;

				if(!m_itrs.find(a,varName))
					throw std::runtime_error("Variable " + varName + " does not exist.");

				try
				{
					out = boost::get<T>((a->second)[0]);
				}
				catch (boost::bad_get e)
				{
					//failed, *really* try to cast
					//normally needed to cast out the date to a string
					try
					{
						out = boost::lexical_cast<T>((a->second)[0]);
					}
					catch (boost::bad_lexical_cast e)
					{
						throw std::runtime_error("Variable " + varName + " could not be cast correctly");
					}
				}

				return out;
			}


		private:
			friend class Observation;
			typedef tbb::concurrent_hash_map<std::string, Variable::const_iterator ,HashCompare> ConstItrMap;

			ConstItrMap m_itrs;//hods the iterators
		};

	
		
		class const_iterator;
		Observation();
		~Observation();
		
		/*
			Function: Begin
				 Returns an iterator at the start of the observations
			
			Parameters: 
				None

			Throws:
				Never
			
			Returns:   
				Observation::const_iterator - Iterator positioned at the begining of the observation
		*/
		const_iterator Begin();


		/*
			Function: End
				 Returns an iterator of one past the end of the observations
			
			Parameters: 
				None

			Throws:
				Never
			
			Returns:   
				Observation::const_iterator - Iterator positioned one past the end of the observations
		*/
		const_iterator End();

		/*
			Function: OpenFile
				 Opens an observation file. An observation file is organized in a tab, ",", or space delimited  columns, with
				each column representing an independent observation, and each row is a timesteps measurement. For example:
				>Date					Rh	Tair	Precip	Notes
				>20080220T000000		50	-12		2		Station_1
				>20080221T000015		40	-10		0		Station_1
				>		[...]
				Some restrictions:
					- No more than 2147483647 steps. At 1s intervals, this equates to roughly 68 years.
					- Consistent units. You mustn't have mm on one line, then meters on the next, for the same observation
					- Has to be on a constant time step. The first interval is taken as the interval for the rest of the file
					- Missing values are not currently allowed - that is, each row must be complete with n entries where n is number of variables.
					- Whitespace, tab or comma delimited. Allows for mixed usage. ex 1234, 4543 890 is legal
					- Values can be one of
						- String (special characters are allowed, no commas)
							Will match any thing with:
							>~`!@#$%^&*(){[}]|\:;"'<>.?/
							>OR has upper case / lower case letters
						- Integer
							Will match the following styles:
							>+1234
							>-1234
							>1234567890
						- Floating point
							Will match the following styles:
							>12.34
							>12.
							>.34
							>12.345
							>1234.45
							>+12.34
							>-12.34
							>+1234.567e-89
							>-1234.567e89
						- Time
							- Must be in one column in the following ISO 8601 date time form:
								- 20080131T235959
								  YYYYMMDDThhmmss

	
			Parameters: 
				std::string path - Fully quantified path to the file
			
			Throws:
				std::runtime_error - On error
			
			Returns:   
				 void 
		*/
		void OpenFile(std::string path);

		/*
			Function: ToFile
				 Writes the internal observation structure to file. Order of columns is not preserved due to how the data is stored.
			
			Parameters: 
				std::string file - File to save to
			
			Throws:
				std::runtime_error On error
			
			Returns:   
				void 
		*/
		void ToFile(std::string file);

		

		/*
			Function: IsOpen
				 Determines if a file was successfully opened
			
			Parameters: 
			
			Throws:
				Never
			
			Returns:   
				bool - True if opened
		*/
		bool IsOpen();

		
		/*
		Class: const_iterator
		Used to iterate over a Observation instance.
		Thread safe.
		Dereference returns a Timestep object.
		Example:
		>Borealis::Observation obs;
		>obs.OpenFile("Observations.txt");
		>
		>Borealis::Observation::const_iterator itr;
		>
		>for(itr=obs.Begin();itr != obs.End(); itr++)
		>{	
		>std::cout << std::endl << (*itr).ToString() << std::endl;		
		>}
		See also:
			<Timestep>
		*/
		class const_iterator : public boost::iterator_facade<
			const_iterator,
			Variable,
			boost::random_access_traversal_tag,
			Timestep>
		{
		public:
			const_iterator();
			const_iterator(const const_iterator& src);
			~const_iterator();
			const_iterator& operator=(const const_iterator& rhs);
		private:
			//the following satisfies the reqs for a boost::facade bidirectional iterator
			friend class boost::iterator_core_access;
			friend class Observation;

			const Observation::Timestep& dereference() const;
			bool equal(const_iterator const& other) const;
			void increment();
			void decrement();

			//iterators for the current step
			Observation::Timestep m_currentStep;

		};

	


	};
	/*
	Class: Station
	Concept of a met station.
	Allows the station to have a location (x,y) and a station ID. 
	As well, it wraps the iterators of the Observation instance.

	Example:
	>Borealis::Station s("ExampleStation1","obs.txt",5,400);
	>s.Now().Get<int>("RH");
	>s.Next();
	*/
	class Station : boost::noncopyable
	{
	public:
		/*
		Function: Station
		Default constructor

		Parameters: 
		None

		Throws:
		Never

		Returns:   
		- 
		*/
		Station();

		/*
		Function: Station
		Creates a station from 

		Parameters: 
		std::string ID - Station ID
		std::string file - Met file to open
		unsigned int x - X coord
		unsigned int y - Y coord
		float elevation - Station elevation

		Throws:
		std::runtime_error - on error

		Returns:   
		- 
		*/
		Station(std::string ID, std::string file, unsigned int x, unsigned int y, float elevation);

		/*
		Function: OpenFile
		Opens a given met file

		Parameters: 
		std::string file - path to met file.

		Throws:
		std::runtime_error - on error

		Returns:   
		void 
		*/
		void OpenFile(std::string file);


		/*
		Function: Now
		Returns the data for the current time step

		Parameters: 
		None

		Throws:
		Never

		Returns:   
		Observation::Timestep - The current timestep. See <Timestep>
		*/
		Observation::Timestep Now();


		/*
		Function: Next
		Gets the next timestep	

		Parameters: 
		None

		Throws:
		Never

		Returns:   
		bool - Returns true if this is the last timestep
		*/
		bool Next();

		/*
		Function: GetX
		Gets the X coordinate. Begins a 0.

		Parameters: 
		None

		Throws:
		Never

		Returns:   
		unsigned int - X corrdinate of the station
		*/
		unsigned int GetX();

		/*
		Function: GetY
		Gets the Y coordinate. Begins at 0

		Parameters: 
		None

		Throws:
		Never

		Returns:   
		unsigned int - Y corrdinate of the station
		*/
		unsigned int GetY();


		/*
		Function: SetX
		Sets the X coordinate. Begins at 0

		Parameters: 
		unsigned int x - X coordinate

		Throws:
		Never

		Returns:   
		void - 
		*/
		void SetX(unsigned int x);


		/*
		Function: SetY
		Sets the Y coordinate. Begins at 0

		Parameters: 
		unsigned int y - Y coordinate

		Throws:
		Never

		Returns:   
		void - 
		*/
		void SetY(unsigned int y);

		
		/*
			Function: GetElevation
				 Returns the station's elevation
			
			Parameters: 
			
			Throws:
				Never
			
			Returns:   
				float - Station elevation
		*/
		float GetElevation();


		/*
			Function: SetElevation
				Sets the station elevation	 
			
			Parameters: 
				float elevation - Station elevation
			
			Throws:
				Never
			
			Returns:   
				void 
		*/
		void SetElevation(float elevation);

	private:
		std::string m_ID;
		Observation* m_obs;
		Observation::const_iterator m_itr;
		unsigned int m_x;
		unsigned int m_y;
		float m_elevation;
	};

}

#endif